package com.example.bledemo;

import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SwitchCompat;

import android.Manifest;
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import com.example.bledemo.adapter.DeviceListAdapter;
import com.example.bledemo.data.Devices;
import com.example.bledemo.utils.DataFormatUtil;
import com.example.bledemo.utils.PermissionUtil;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * 作者 : Leo
 * 邮箱 : wang100328@petalmail.com
 * 时间 : 2022/01/14
 * 描述 : BLE低功耗蓝牙开发
 */
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private static final String TAG = "MainActivity";
    private Context mContext;
    private EditText mEtMessage;
    private TextView mTvReceive, mTvState;
    private BluetoothGatt mBtGatt;
    private BluetoothAdapter mBtAdapter;
    private DeviceListAdapter mDeviceAdapter;
    private BluetoothGattCharacteristic mWriteBtGattCharacteristic;

    //UUID全局变量
    private UUID UUID_WRITE_SERVICES;
    private UUID UUID_WRITE_CHARA;
    private UUID UUID_NOTIFY_CHARA;

    /**
     * 查重数组
     */
    private final List<String> duplicateData = new ArrayList<>();

    /**
     * 设备列表（设备名称、MAC地址）
     */
    private final List<Devices> devices = new ArrayList<>();

    /**
     * 服务回调状态标记符
     */
    private static boolean isGattSuccess = false;

    /**
     * 权限请求码
     */
    private final int mRequestCode = 0x01;

    /**
     * 权限数组
     */
    @RequiresApi(api = Build.VERSION_CODES.S)
    private final String[] permissions = new String[]{
            Manifest.permission.ACCESS_COARSE_LOCATION,
            Manifest.permission.ACCESS_FINE_LOCATION,
            Manifest.permission.BLUETOOTH_CONNECT,
            Manifest.permission.BLUETOOTH_SCAN,
    };

    @SuppressLint("UseSwitchCompatOrMaterialCode")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTvState = findViewById(R.id.tv_main_state);
        mEtMessage = findViewById(R.id.et_main_send);
        mTvReceive = findViewById(R.id.tv_main_receive);
        SwitchCompat btSwitch = findViewById(R.id.st_main_blue);
        Button btnSend = findViewById(R.id.btn_main_send);
        Button btnDisConnect = findViewById(R.id.btn_main_disconnect);
        ListView listView = findViewById(R.id.lv_main_device_list);

        btnDisConnect.setOnClickListener(this);
        btnSend.setOnClickListener(this);

        getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); //透明状态栏
        getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);//状态栏字体变暗

        //蓝牙开关
        btSwitch.setChecked(false); //默认关闭
        btSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {

            @SuppressLint("MissingPermission")
            @Override
            public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
                if (b) {
                    initBluetooth();
                    scanBluetooth();
                } else {
                    Log.i(TAG, "onCheckedChanged: 取消扫描，清空设备列表，断开设备连接");
                    mBtAdapter.cancelDiscovery();
                    disConnected(isGattSuccess);
                    devices.clear();
                    mDeviceAdapter.notifyDataSetChanged();
                }
            }
        });

        //设备列表
        mDeviceAdapter = new DeviceListAdapter(this, devices);
        listView.setAdapter(mDeviceAdapter);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Devices table = devices.get(position); //获取点击item所在数组中的索引
                BluetoothDevice device = mBtAdapter.getRemoteDevice(table.getAddress()); //获取蓝牙MAC地址
                connectBluetooth(device, mContext); //连接蓝牙
                Log.i(TAG, "onItemClick: 连接蓝牙:" + table.getName() + " MAC地址:" + table.getAddress());
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        mTvState.setText(getString(R.string.connecting));
                    }
                });
            }
        });

        //动态申请权限
        PermissionUtil.checkPermission(this, mRequestCode, permissions);
    }

    @SuppressLint({"MissingPermission", "NonConstantResourceId"})
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_main_disconnect:
                disConnected(isGattSuccess);
                break;
            case R.id.btn_main_send:
                //判断服务是否为空，防止空对象引用错误
                if (mWriteBtGattCharacteristic == null) {
                    Log.i(TAG, "send: 写入服务为空");
                } else {
                    Log.i(TAG, "send: 开始发送数据");
                    String strSend = mEtMessage.getText().toString();
                    sendMsg(strSend);
                }
                break;
        }
    }

    /**
     * 初始化蓝牙
     */
    @SuppressLint("MissingPermission")
    public void initBluetooth() {
        //获取蓝牙默认适配器
        mBtAdapter = BluetoothAdapter.getDefaultAdapter();

        //判断设备是否支持低功耗蓝牙
        if (mBtAdapter == null) {
            Log.i(TAG, "openBluetooth: 该设备不支持低功耗蓝牙");
        } else {
            //打开蓝牙
            if (!mBtAdapter.isEnabled()) {
                mBtAdapter.enable();
                Log.i(TAG, "openBluetooth: 已打开蓝牙");
            } else {
                Log.i(TAG, "initBluetooth: 蓝牙已打开");
            }
        }
    }

    /**
     * 查找低功耗蓝牙设备
     */
    @SuppressLint("MissingPermission")
    private void scanBluetooth() {
        Log.i(TAG, "scanBluetooth: 搜索低功耗蓝牙");
        mBtAdapter.startLeScan(mBtLeScanCallback);
    }

    /**
     * 扫描回调
     */
    private final BluetoothAdapter.LeScanCallback mBtLeScanCallback = new BluetoothAdapter.LeScanCallback() {
        @SuppressLint("MissingPermission")
        @Override
        public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
            //跳过设备名字为空的蓝牙
            if (device.getName() == null) return;

            //将蓝牙设备添加进Devices集合
            Devices tmp = new Devices();
            tmp.setName(device.getName());
            tmp.setAddress(device.getAddress());
            String str = device.getAddress();
            //查重
            if (!duplicateData.contains(str)) {
                duplicateData.add(str);
                devices.add(tmp);
            }
            mDeviceAdapter.notifyDataSetChanged();
            Log.i(TAG, "onLeScan:设备数组包含的数据:" + devices);
        }
    };

    /**
     * 连接蓝牙
     *
     * @param device  目标设备
     * @param context 上下文对象
     */
    @SuppressLint("MissingPermission")
    private void connectBluetooth(BluetoothDevice device, Context context) {
        Log.i(TAG, "connectBluetooth: 关闭蓝牙搜索");
        this.mContext = context;
        //关闭蓝牙搜索，连接蓝牙之前关闭蓝牙搜索，因为搜索过程非常耗电。
        mBtAdapter.stopLeScan(mBtLeScanCallback);
        //设置延迟，保证搜索完全关闭，再开始连接蓝牙。
        Handler handler = new Handler();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                Log.i(TAG, "run: 连接蓝牙");
                //连接蓝牙：autoConnect（布尔值，指示是否在可用时自动连接到BLE设备）
                mBtGatt = device.connectGatt(context, false, mBtGattCallback);
            }
        }, 1000);
    }

    /**
     * 蓝牙服务回调（建立通信）
     */
    private final BluetoothGattCallback mBtGattCallback = new BluetoothGattCallback() {

        //成功连接到设备调用此方法
        @SuppressLint("MissingPermission")
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            //判断蓝牙是否连接成功
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                //发现设备服务
                gatt.discoverServices();
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        mTvState.setText(getString(R.string.connection_succeeded));
                    }
                });
                Log.i(TAG, "onConnectionStateChange: 连接成功");

            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                //关闭回调服务（等于断开蓝牙连接）
                mBtGatt.close();
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        mTvState.setText(getString(R.string.connection_failed));
                    }
                });
                Log.i(TAG, "onConnectionStateChange: 连接失败");
            }
        }

        //发现服务，在设备连接成功后调用，扫描到设备服务后调用此方法。
        @SuppressLint("MissingPermission")
        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            //判断回调服务是否成功
            if (status == BluetoothGatt.GATT_SUCCESS) {
                isGattSuccess = true;
                Log.i(TAG, "onServicesDiscovered: 回调服务连接成功");
            } else {
                isGattSuccess = false; //状态标记
                Log.i(TAG, "onServicesDiscovered: 回调服务连接失败" + status);
            }

            //未知设备UUID情况下，用下列方法找出对应的UUID。
            List<BluetoothGattService> gattServices = gatt.getServices(); //获取服务UUID并添加进集合
            for (BluetoothGattService service : gattServices) {
                List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics(); //获取特征UUID并添加进集合
                for (BluetoothGattCharacteristic characteristic : characteristics) {
                    //查找并识别出监听、写入等特征UUID和对应的服务UUID
                    int charaProp = characteristic.getProperties();
                    if ((charaProp & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0) {
                        UUID_WRITE_CHARA = characteristic.getUuid(); //获取写入特征UUID
                        UUID_WRITE_SERVICES = service.getUuid(); //获取写入服务UUID
                        Log.i(TAG, "onServicesDiscovered: 写入服务UUID:" + UUID_WRITE_CHARA);
                        Log.i(TAG, "onServicesDiscovered: 写入特征UUID:" + UUID_WRITE_SERVICES);
                    }
                    if ((charaProp & BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {
                        UUID_NOTIFY_CHARA = characteristic.getUuid(); //获取监听特征UUID
                        Log.i(TAG, "onServicesDiscovered: 监听特征UUID:" + UUID_NOTIFY_CHARA);
                    }
                }
            }

            //设置服务UUID
            BluetoothGattService gattService = mBtGatt.getService(UUID_WRITE_SERVICES);
            //设置写入特征UUID
            mWriteBtGattCharacteristic = gattService.getCharacteristic(UUID_WRITE_CHARA);
            //设置监听特征UUID
            BluetoothGattCharacteristic notifyBtGattCharacteristic = gattService.getCharacteristic(UUID_NOTIFY_CHARA);
            //开启监听
            gatt.setCharacteristicNotification(notifyBtGattCharacteristic, true);
        }

        //开启监听，即建立与设备的通信的首发数据通道，BLE开发中只有当上位机成功开启监听后才能与下位机收发数据.开启监听成功调用此方法。
        @Override
        public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
            super.onDescriptorWrite(gatt, descriptor, status);
            if (status == BluetoothGatt.GATT_SUCCESS) {
                Log.i(TAG, "onDescriptorWrite: 开启监听成功");
            }
        }

        //写入数据，若写入指令成功则调用此方法，说明将数据已经发送给下位机。
        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicWrite(gatt, characteristic, status);
            if (status == BluetoothGatt.GATT_SUCCESS) {
                Toast.makeText(MainActivity.this, getString(R.string.data_sent_successfully), Toast.LENGTH_SHORT).show();
                Log.i(TAG, "onCharacteristicWrite: 数据发送成功");
            }
        }

        //接收数据，若发送的数据符合通信协议，则下位机会向上位机回复相应的数据。发送的数据通过此方法获取。
        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            byte[] value = characteristic.getValue(); //value为设备发送的数据，根据数据协议进行解析。
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    mTvReceive.setText(DataFormatUtil.arrayToHex(value));
                }
            });
            Log.i(TAG, "onCharacteristicChanged: 蓝牙发送过来的数据:" + DataFormatUtil.arrayToHex(value));
        }
    };

    /**
     * 发送数据
     *
     * @param data 数据
     */
    @SuppressLint("MissingPermission")
    private void sendMsg(String data) {
        Log.i(TAG, "sendMsg: 发送的数据:" + data);
        mWriteBtGattCharacteristic.setValue(DataFormatUtil.arrayToHex(DataFormatUtil.stringToBytes(data))); //设置写入，setValue(发送的数据)
        mWriteBtGattCharacteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE); //设置写入特征UUID
        mBtGatt.writeCharacteristic(mWriteBtGattCharacteristic); //向设备写入指令。
    }

    /**
     * 断开蓝牙连接
     *
     * @param b 判断蓝牙服务回调是否成功（防止空对象异常）
     */
    @SuppressLint("MissingPermission")
    private void disConnected(boolean b) {
        if (b) {
            Log.i(TAG, "disConnected: 断开蓝牙连接");
            //断开连接
            mBtGatt.disconnect();
        }
    }

    /**
     * 权限申请结果回调
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        //是否授权标识符
        boolean hasPermissionDismiss = false;

        if (mRequestCode == requestCode) {
            for (int results : grantResults) {
                //如果有未授权权限
                if (results == -1) {
                    hasPermissionDismiss = true;
                    break;
                }
            }

            if (hasPermissionDismiss)
                Toast.makeText(this, getString(R.string.please_grant_app_permission), Toast.LENGTH_SHORT).show();
        }
    }
}